package asapi

import (
	"context"
	"encoding/json"
	"fmt"
	"jgt-sdk/utils"
	"log"
	"net/url"
	"os"
	"sync"
	"time"

	"github.com/coocood/freecache"
)

// NewAPIHandle 创建API请求处理
func NewAPIHandle(cfg *Config) *APIHandle {
	if cfg == nil {
		cfg = defaultConfig
	}

	if cfg.CacheSize == 0 {
		cfg.CacheSize = defaultConfig.CacheSize
	}

	return &APIHandle{
		cfg:      cfg,
		th:       NewTokenHandle(cfg),
		hostLock: new(sync.RWMutex),
		hostData: make(map[string]string),
		cache:    freecache.NewCache(cfg.CacheSize),
		logger:   log.New(os.Stderr, "[SDK]", log.LstdFlags),
	}
}

// APIHandle API请求处理
type APIHandle struct {
	cfg      *Config
	th       *TokenHandle
	hostLock *sync.RWMutex
	hostData map[string]string
	cache    *freecache.Cache
	traceID  string
	logger   *log.Logger
}

// GetConfig 获取配置参数
func (a *APIHandle) GetConfig() *Config {
	return a.cfg
}

// GetTokenHandle 获取令牌处理
func (a *APIHandle) GetTokenHandle() *TokenHandle {
	return a.th
}

// GetCache 获取缓存
func (a *APIHandle) GetCache() *freecache.Cache {
	return a.cache
}

// TokenInfo 令牌信息
type TokenInfo struct {
	UserID    string `json:"user_id"`
	ClientID  string `json:"client_id"`
	ExpiresIn int64  `json:"expires_in"`
}

// CacheItem 缓存数据项
type CacheItem struct {
	ExpiredAt time.Time
	Token     *TokenInfo
}

func (a *APIHandle) getContext() context.Context {
	return utils.NewTraceIDContext(context.Background(), a.traceID)
}

// VerifyToken 验证访问令牌
func (a *APIHandle) VerifyToken(token string) (*TokenInfo, error) {
	cacheKey := fmt.Sprintf("c_token_%s", token)

	var cItem CacheItem
	exists := a.GetCacheValue(cacheKey, &cItem)
	if exists {
		if time.Now().Before(cItem.ExpiredAt) {
			return cItem.Token, nil
		}
	}

	param := make(url.Values)
	param.Set("access_token", token)
	param.Set("service", a.cfg.ServiceIdentify)

	resp, err := utils.Get(a.getContext(), a.cfg.GetURL("/oauth2/verify"), param)
	if err != nil {
		return nil, err
	} else if resp.Response().StatusCode != 200 {
		v, verr := resp.String()
		if verr != nil {
			return nil, verr
		}
		return nil, fmt.Errorf(v)
	}

	var item TokenInfo
	err = resp.JSON(&item)
	if err != nil {
		return nil, err
	}

	expiredAt := time.Duration(item.ExpiresIn) * time.Second
	a.SetCacheValue(
		cacheKey,
		&CacheItem{ExpiredAt: time.Now().Add(expiredAt), Token: &item},
		int(item.ExpiresIn),
	)

	return &item, nil
}

// LoginRequest 登录请求参数
type LoginRequest struct {
	UserName  string `json:"user_name"`  // 用户名
	Password  string `json:"password"`   // 密码
	LoginType int    `json:"login_type"` // 登录方式(10:QQ 20:微信 30:微博 40:用户名(包含手机号))
}

// Login 用户登录
func (a *APIHandle) Login(req *LoginRequest) (string, error) {
	var result struct {
		UserID string `json:"user_id"`
	}

	err := utils.PostJSONWithToken(a.getContext(), a.cfg.GetURL("/api/login"), a.th.GetWithTraceID(a.traceID), req, &result)
	if err != nil {
		return "", err
	}
	return result.UserID, nil
}

// AddUserRequest 增加用户请求参数
type AddUserRequest struct {
	UserName     string `json:"user_name"`     // 用户名
	Tel          string `json:"tel"`           // 手机号
	Password     string `json:"password"`      // 密码(md5加密后转大写的值)
	PlatformType int    `json:"platform_type"` // 10 QQ，20 微信，30 微博
	PlatformID   string `json:"platform_id"`   // 平台唯一ID
}

// AddUser 增加用户
func (a *APIHandle) AddUser(req *AddUserRequest) (string, error) {
	var result struct {
		UserID string `json:"user_id"`
	}

	err := utils.PostJSONWithToken(a.getContext(), a.cfg.GetURL("/api/user"), a.th.GetWithTraceID(a.traceID), req, &result)
	if err != nil {
		return "", err
	}
	return result.UserID, nil
}

// GetServiceHost 获取服务HOST
func (a *APIHandle) GetServiceHost(identify string) (string, error) {
	a.hostLock.RLock()
	host := a.hostData[identify]
	a.hostLock.RUnlock()

	if host == "" {
		a.hostLock.Lock()
		defer a.hostLock.Unlock()

		var result struct {
			Host string `json:"host"`
		}

		err := utils.GetWithToken(a.getContext(), a.cfg.GetURL(fmt.Sprintf("/api/service/%s", identify)), a.th.GetWithTraceID(a.traceID), nil, &result)
		if err != nil {
			return "", err
		}

		host = result.Host
		a.hostData[identify] = host
	}

	return host, nil
}

// CheckTel 检查手机号
func (a *APIHandle) CheckTel(tel string) (bool, error) {
	var result struct {
		Tel bool `json:"tel"`
	}

	param := make(url.Values)
	param.Set("tel", tel)
	err := utils.GetWithToken(a.getContext(), a.cfg.GetURL("/api/user/check"), a.th.GetWithTraceID(a.traceID), param, &result)
	if err != nil {
		return false, err
	}

	return result.Tel, nil
}

// CheckQQ 检查qq
func (a *APIHandle) CheckQQ(qq string) (bool, error) {
	var result struct {
		QQ bool `json:"qq"`
	}

	param := make(url.Values)
	param.Set("qq", qq)
	err := utils.GetWithToken(a.getContext(), a.cfg.GetURL("/api/user/check"), a.th.GetWithTraceID(a.traceID), param, &result)
	if err != nil {
		return false, err
	}

	return result.QQ, nil
}

// CheckWeixin 检查微信
func (a *APIHandle) CheckWeixin(weixin string) (bool, error) {
	var result struct {
		Weixin bool `json:"weixin"`
	}

	param := make(url.Values)
	param.Set("weixin", weixin)
	err := utils.GetWithToken(a.getContext(), a.cfg.GetURL("/api/user/check"), a.th.GetWithTraceID(a.traceID), param, &result)
	if err != nil {
		return false, err
	}

	return result.Weixin, nil
}

// CheckWeibo 检查微博
func (a *APIHandle) CheckWeibo(weibo string) (bool, error) {
	var result struct {
		Weibo bool `json:"weibo"`
	}

	param := make(url.Values)
	param.Set("weibo", weibo)
	err := utils.GetWithToken(a.getContext(), a.cfg.GetURL("/api/user/check"), a.th.GetWithTraceID(a.traceID), param, &result)
	if err != nil {
		return false, err
	}

	return result.Weibo, nil
}

// DeleteUser 删除用户
func (a *APIHandle) DeleteUser(userID string) error {
	var result string
	err := utils.DeleteWithToken(a.getContext(), a.cfg.GetURL("/api/user/"+userID), a.th.GetWithTraceID(a.traceID), &result)
	if err != nil {
		return err
	} else if result == "ok" {
		return nil
	}

	return fmt.Errorf(result)
}

// UpdatePwdByTEL 根据手机号更新密码
func (a *APIHandle) UpdatePwdByTEL(tel, password string) error {
	var result string

	body := map[string]interface{}{
		"tel":      tel,
		"password": password,
	}

	err := utils.PostJSONWithToken(a.getContext(), a.cfg.GetURL("/api/user/updatepwdbytel"), a.th.GetWithTraceID(a.traceID), body, &result)
	if err != nil {
		return err
	} else if result == "ok" {
		return nil
	}

	return fmt.Errorf(result)
}

// UpdatePlatformByTel 根据手机号更新第三方平台
// platformType 平台类型（10 QQ，20 微信，30 微博）
// platformID 平台ID
func (a *APIHandle) UpdatePlatformByTel(tel string, platformType int, platformID string) error {
	var result string

	body := map[string]interface{}{
		"tel":           tel,
		"platform_type": platformType,
		"platform_id":   platformID,
	}
	err := utils.PostJSONWithToken(a.getContext(), a.cfg.GetURL("/api/user/updateplatformbytel"), a.th.GetWithTraceID(a.traceID), body, &result)
	if err != nil {
		return err
	} else if result == "ok" {
		return nil
	}

	return fmt.Errorf(result)
}

// UpdatePwd 根据手机号更新密码
func (a *APIHandle) UpdatePwd(userID, oldPassword, newPassword string) error {
	var result string

	body := map[string]interface{}{
		"old_password": oldPassword,
		"new_password": newPassword,
	}

	err := utils.PostJSONWithToken(a.getContext(), a.cfg.GetURL(fmt.Sprintf("/api/user/%s/pwd", userID)), a.th.GetWithTraceID(a.traceID), body, &result)
	if err != nil {
		return err
	} else if result == "ok" {
		return nil
	}

	return fmt.Errorf(result)
}

// UpdateTel 更新手机号
func (a *APIHandle) UpdateTel(userID, oldTel, newTel string) error {
	var result string

	body := map[string]interface{}{
		"old_tel": oldTel,
		"new_tel": newTel,
	}

	err := utils.PostJSONWithToken(a.getContext(), a.cfg.GetURL(fmt.Sprintf("/api/user/%s/tel", userID)), a.th.GetWithTraceID(a.traceID), body, &result)
	if err != nil {
		return err
	} else if result == "ok" {
		return nil
	}

	return fmt.Errorf(result)
}

// GetUserInfoReply 获取用户信息响应参数
type GetUserInfoReply struct {
	Tel      string `json:"tel"`       // 手机号
	UserName string `json:"user_name"` // 用户名
	Status   int    `json:"status"`    // 1:正常 2:冻结
}

// GetUserInfo 获取用户信息
func (a *APIHandle) GetUserInfo(userID string, disable ...bool) (*GetUserInfoReply, error) {
	cacheKey := fmt.Sprintf("c_auth_user_id_%s", userID)

	var cItem GetUserInfoReply
	exists := a.GetCacheValue(cacheKey, &cItem, disable...)
	if exists {
		return &cItem, nil
	}

	var result GetUserInfoReply
	err := utils.GetWithToken(a.getContext(), a.cfg.GetURL(fmt.Sprintf("/api/user/%s", userID)), a.th.GetWithTraceID(a.traceID), nil, &result)
	if err != nil {
		return nil, err
	}
	a.SetCacheValue(cacheKey, result, 300)

	return &result, nil
}

// GetCacheValue 获取缓存数据
func (a *APIHandle) GetCacheValue(key string, result interface{}, disable ...bool) bool {
	if len(disable) > 0 && disable[0] {
		return false
	}

	buf, err := a.cache.Get([]byte(key))
	if err != nil {
		if err == freecache.ErrNotFound {
			return false
		}
		a.logger.Printf("获取缓存数据发生错误：%s", err.Error())
		return false
	} else if buf != nil {
		err = json.Unmarshal(buf, result)
		if err != nil {
			a.logger.Printf("解析缓存数据发生错误：%s", err.Error())
			return false
		}
		return true
	}
	return false
}

// SetCacheValue 设置缓存数据
func (a *APIHandle) SetCacheValue(key string, value interface{}, expireSeconds int) {
	buf, err := json.Marshal(value)
	if err != nil {
		a.logger.Printf("序列化缓存数据发生错误：%s", err.Error())
		return
	}

	err = a.cache.Set([]byte(key), buf, expireSeconds)
	if err != nil {
		a.logger.Printf("设置缓存数据发生错误：%s", err.Error())
	}
}
